package com.foodev.maven.gwt;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.maven.artifact.DependencyResolutionRequiredException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.codehaus.plexus.util.FileUtils;


/**
 * @execute phase="process-resources"
 * @goal shell
 * @requiresDependencyResolution test
 * @author Martin Algesten
 */
public class ShellMojo extends AbstractGwtScriptMojo {

    /**
     * Prevents the embedded Tomcat server from running, even if a port is
     * specified
     * 
     * @parameter
     */
    private boolean noserver = false;

    /**
     * Runs an embedded Tomcat instance on the specified port (defaults to 8888)
     * 
     * @parameter
     */
    private int port = -1;

    /**
     * Allows the user to browse URLs that match the specified regexes (comma or
     * space separated)
     * 
     * @parameter
     */
    private String whiteList;

    /**
     * Prevents the user browsing URLs that match the specified regexes (comma
     * or space separated)
     * 
     * @parameter
     */
    private String blackList;

    /**
     * Specifies a different embedded web server to run (must implement
     * ServletContainerLauncher)
     * 
     * @parameter
     */
    private String server;

    /**
     * Automatically launches the specified URL
     * 
     * @parameter
     */
    private String startupUrl;


    public void execute() throws MojoExecutionException, MojoFailureException {

        try {

            extractNativeLibs();

            File file = createScriptFile("shell");
            FileWriter fwriter = new FileWriter(file);
            PrintWriter writer = new PrintWriter(fwriter);

            // shell should not keep the 'target/classes'
            List<String> classpath = filterClasspath(buildClasspath(), true);

            startScript(writer);

            writeOutputDirs(writer);
            writeClasspath(writer, classpath);

            writeCommand(writer);

            writer.close();
            fwriter.close();

            int exitValue = executeProcess(gwtHome, new String[] { file
                    .getAbsolutePath() });

            if (exitValue != 0)
                throw new MojoExecutionException("GWT shell failed."
                        + " Exit value: " + exitValue);

        } catch (MojoExecutionException e) {

            throw e;

        } catch (Exception e) {

            getLog().error(e);

            throw new MojoFailureException("GWT compilation failed.");

        }

    }


    private void writeOutputDirs(PrintWriter writer) throws IOException {

        if (isWindows()) {
            writer.print("set OUTPUTDIRS=");
        } else {
            writer.print("export OUTPUTDIRS=");
        }

        writer.println(project.getBuild().getOutputDirectory() + ","
                + project.getBuild().getTestOutputDirectory());

    }


    /**
     * Writes the hosted mode command to the script.
     * 
     * @param writer The writer to write the command to.
     * @throws IOException if something goes wrong when writing.
     */
    private void writeCommand(PrintWriter writer) throws IOException,
            DependencyResolutionRequiredException, MojoExecutionException {

        writeJavaCommand(writer);
        
        if (isMac()) {
            writer.write(" -XstartOnFirstThread");
        }

        writeSharedVMArgs(writer);

        // writer.write(" com.google.gwt.dev.HostedMode");
        writer.write(" " + ClasspathFixShellLauncher.class.getName());

        writeSharedArgs(writer, webappSrc);

        if (noserver)
            writer.write(" -noserver");

        if (port != -1)
            writer.write(" -port " + port);

        if (whiteList != null)
            writer.write(" -whiteList " + whiteList);

        if (blackList != null)
            writer.write(" -blackList " + blackList);

        if (server == null) {
            writer.write(" -server "
                    + MushedClasspathJettyLauncher.class.getName());
        } else {
            writer.write(" -server " + server);
        }

        if (startupUrl != null)
            writer.write(" -startupUrl " + startupUrl);

        writeModules(writer);

        writer.println();

    }


    /**
     * Extracts the native libs from the .zip found on the classpath.
     * 
     * @throws MojoExecutionException
     */
    private void extractNativeLibs() throws MojoExecutionException, IOException {

        URL url = findNativeZip();

        if (url == null)
            throw new MojoExecutionException(
                    "No classloader is of type URLClassLoader");

        if (!"file".equals(url.getProtocol())) {
            throw new MojoExecutionException("Native library zip is not a "
                    + "file URL: " + url.toString());
        }

        File dir = new File(url.getPath()).getParentFile();

        ZipInputStream zip = new ZipInputStream(url.openStream());
        ZipEntry entry;
        byte[] buf = new byte[1024];
        int read;

        while ((entry = zip.getNextEntry()) != null) {

            File outFile = new File(dir, entry.getName());

            if (outFile.exists()) {
		if (outFile.isDirectory()) {
                  FileUtils.deleteDirectory(outFile);
		} else {
		    outFile.delete();
		}
	    }

		
            if (entry.isDirectory()) {

                if (!outFile.mkdirs())
                    throw new RuntimeException("Failed to make dir: "
                            + outFile.getAbsolutePath());

            } else {
                FileOutputStream fos = new FileOutputStream(outFile);

                while ((read = zip.read(buf)) != -1)
                    fos.write(buf, 0, read);

                outFile.setLastModified(entry.getTime());

                fos.close();
            }

        }

    }


    /**
     * Finds the native zip on the classpath, first looks at the thread context
     * classloader, then the class classloader.
     * 
     * @return the URL for the found zip or null if not found.
     * @throws MojoExecutionException
     */
    private URL findNativeZip() {

        URL url = null;
        ClassLoader cl = null;

        cl = Thread.currentThread().getContextClassLoader();
        if (!(cl instanceof URLClassLoader)) {
            getLog().info("Context ClassLoader is not a URLClassLoader");
        } else {
            url = findZip((URLClassLoader)cl);
        }

        if (url != null)
            return url;

        cl = getClass().getClassLoader();
        if (!(cl instanceof URLClassLoader)) {
            getLog().info("Local ClassLoader is not a URLClassLoader");
        } else {
            url = findZip((URLClassLoader)cl);
        }

        if (url != null)
            return url;

        return null;

    }


    /**
     * Goes through the URLs in a URLClassLoader and find the entry matching the
     * name in ZIPREGEX.
     * 
     * @param cl the classloader to inspect.
     * @return the found .zip or null.
     */
    private URL findZip(URLClassLoader cl) {

        URL[] urls = cl.getURLs();

        for (int i = 0; i < urls.length; i++) {

            if (isNativeLibZip(urls[i].toString())) {

                return urls[i];

            }

        }

        return null;

    }

}
